﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;

namespace HCore
{
    public class ClientNetworkManager
    {
        private static readonly Dictionary<uint, ClientNetworkManager> Id2Manager =
            new Dictionary<uint, ClientNetworkManager>();

        private static uint NextManagerId = 1;

        public static ClientNetworkManager Get(string serverIp, int serverPort)
        {
            return new ClientNetworkManager(NextManagerId++, serverIp, serverPort);
        }

        private static int OutPut(IntPtr buf, int len, IntPtr kcp, IntPtr user)
        {
            uint address = (uint) user.ToInt32();
            uint managerId = address >> NetworkDefine.SESSION_BITS & NetworkDefine.MANAGER_MASK;
            if (Id2Manager.TryGetValue(managerId, out var manager))
            {
                uint sessionId = address & NetworkDefine.SESSION_MASK;
                manager.Output(sessionId, buf, len);
            }

            return 0;
        }

        private enum ConnectState
        {
            // 未连接
            Unconnected,

            // 连接中
            Connecting,

            // 已连接
            Connected,
        }

        private readonly uint _id;
        private readonly Stopwatch _stopwatch;
        private readonly UdpSocket _socket;
        private readonly EndPoint _connectEndPoint;
        private readonly Dictionary<byte, Action<InputMemoryBitStream>> _inputHandles;
        private readonly byte[] cacheBytes = new byte[128 * 1500];
        private ConnectState _connectState;
        private long _nextRetryTime;
        private Session _session;

        private ClientNetworkManager(uint id, string connectIp, int connectPort)
        {
            _id = id;
            _socket = new DelayUdpSocket(NetworkDefine.LOST_RATE, NetworkDefine.RTT_MIN, NetworkDefine.RTT_MAX, "client");
            _connectEndPoint = new IPEndPoint(IPAddress.Parse(connectIp), connectPort);
            _inputHandles = new Dictionary<byte, Action<InputMemoryBitStream>>();
            _stopwatch = new Stopwatch();
            _stopwatch.Start();
            _connectState = ConnectState.Unconnected;

            Id2Manager.Add(_id, this);
        }

        public bool Register(byte packetType, Action<InputMemoryBitStream> handle)
        {
            if (_inputHandles.ContainsKey(packetType))
            {
                return false;
            }

            _inputHandles.Add(packetType, handle);
            return true;
        }

        public bool Unregister(byte packetType)
        {
            if (!_inputHandles.ContainsKey(packetType))
            {
                return false;
            }

            _inputHandles.Remove(packetType);
            return true;
        }

        public int Send(byte[] bytes, int length)
        {
            return _session.Send(bytes, length);
        }

        public int Send(OutputMemeryBitStream output)
        {
            return Send(output.GetBuffer(), output.GetByteLength());
        }

        public int RawSend(byte[] bytes, int length)
        {
            return _socket.SendTo(bytes, length, _connectEndPoint);
        }

        public int RawSend(OutputMemeryBitStream output)
        {
            return RawSend(output.GetBuffer(), output.GetByteLength());
        }

        public ReceivedPacket ReceivePacket()
        {
            if (_connectState != ConnectState.Connected)
            {
                return null;
            }

            int readByteCount = _session.Rec(cacheBytes, cacheBytes.Length);
            if (readByteCount > 0)
            {
                return new ReceivedPacket(new InputMemoryBitStream(cacheBytes, readByteCount), _session.endPoint);
            }

            return null;
        }

        public void Input()
        {
            while (true)
            {
                ReceivedPacket receivedPacket = _socket.ReceiveFrom();
                if (receivedPacket == null)
                {
                    break;
                }

                if (!receivedPacket.endPoint.Equals(_connectEndPoint))
                {
                    continue;
                }

                InputMemoryBitStream input = receivedPacket.input;
                uint conv = input.ReadUint();
                if (conv == NetworkDefine.CONNECT_MAGIC_NUM)
                {
                    // 建立连接成功
                    if (_session == null)
                    {
                        _session = new Session(_id, input.ReadUint(), receivedPacket.endPoint, OutPut);
                    }

                    _connectState = ConnectState.Connected;
                }
                else if (conv == NetworkDefine.CONNECTING_MAGIC_NUM)
                {
                    //已建立连接
                    if (_session != null)
                    {
                        _session.Input(input.GetBuffer(), input.GetByteLength());
                    }
                }
            }

            while (_connectState == ConnectState.Connected && _session != null)
            {
                int readByteCount = _session.Rec(cacheBytes, cacheBytes.Length);
                if (readByteCount <= 0)
                {
                    break;
                }

                var input = new InputMemoryBitStream(cacheBytes, readByteCount);
                byte packetType = input.ReadByte();

                if (!_inputHandles.TryGetValue(packetType, out var handle))
                {
                    continue;
                }
                handle(input);
            }
        }

        public void Update()
        {
            switch (_connectState)
            {
                case ConnectState.Connected:
                    uint current = (uint) _stopwatch.ElapsedMilliseconds;
                    _session.Update(current);
                    break;
                case ConnectState.Connecting:
                    if (_stopwatch.ElapsedMilliseconds >= _nextRetryTime)
                    {
                        Reconnect();
                    }

                    break;
            }
        }

        public int Output(uint sessionId, IntPtr buf, int len)
        {
            if (_connectState != ConnectState.Connected)
            {
                return 0;
            }

            Debug.Assert(sessionId == _session.sessionId);
            Marshal.Copy(buf, cacheBytes, 0, len);
            return RawSend(cacheBytes, len);
        }

        public bool connected => _connectState == ConnectState.Connected;

        public void Connect()
        {
            if (_connectState == ConnectState.Unconnected || _connectState == ConnectState.Connecting)
            {
                OutputMemeryBitStream output = new OutputMemeryBitStream();
                output.WriteUInt(NetworkDefine.CONNECT_MAGIC_NUM);
                RawSend(output);
                // 100ms后重试
                _nextRetryTime = _stopwatch.ElapsedMilliseconds + 100;
                _connectState = ConnectState.Connecting;
            }
        }

        public void Reconnect()
        {
            if (connected)
            {
                return;
            }

            Logger.Log("Reconnect");
            Connect();
        }

        public void Disconnect()
        {
            if (_connectState == ConnectState.Unconnected)
            {
                return;
            }

            _session.Dispose();
            _session = null;
            _connectState = ConnectState.Unconnected;
        }
    }
}